#include "znx_slru.h"

typedef struct {
    znx_rbtree_node_t       tree_node;
    znx_queue_t             queue;
    int                     idx;
    znx_slru_t              *slru;
    void                    *data;
} znx_slru_item_t;


static inline znx_slru_item_t *
znx_slru_item_create(znx_slru_t *slru, void *data)
{
    znx_slru_item_t *item;
    item = znx_thread_calloc(sizeof(znx_slru_item_t));
    item->slru = slru;
    item->data = data;
    return item;
}


static inline void
znx_slru_item_destroy(znx_slru_item_t *item)
{
    znx_thread_free(item);
}


static inline int
znx_slru_item_cmp(znx_rbtree_node_t *a, znx_rbtree_node_t *b)
{
    znx_slru_item_t *anode, *bnode;
    anode = (znx_slru_item_t *)a;
    bnode = (znx_slru_item_t *)b;
    return anode->slru->cmp_handler(anode->data, bnode->data);
}


znx_slru_t *znx_slru_create(
    int64_t                             nlist,
    int64_t                             max_item,
    int64_t                             max_bytes,
    znx_slru_item_size_get_handler_t    size_get_handler,
    znx_slru_item_free_handler_t        free_handler,
    znx_slru_item_cmp_handler_t         cmp_handler)
{
    if (nlist <= 0) {
        ZNX_DEF_LOG_FATAL(NULL, "nlist must be greate than 0");
    }

    if (max_item <= 0) {
        max_item = -1;
    } else {
        max_item /= nlist;
    }

    if (max_bytes <= 0) {
        max_bytes = -1;
    } else {
        max_bytes /= nlist;
    }

    if (max_item < 0 && max_bytes < 0) {
        ZNX_DEF_LOG_FATAL(NULL, "max_item and max_bytes must have one greate than 0");
    }

    if (cmp_handler == NULL) {
        ZNX_DEF_LOG_FATAL(NULL, "cmp_handler cannot be null");
    }

    znx_slru_t       *slru;
    slru = znx_thread_calloc(sizeof(znx_slru_t));
    slru->nlist = nlist;
    slru->max_item = max_item;
    slru->max_bytes = max_bytes;
    slru->size_get_handler = size_get_handler;
    slru->free_handler = free_handler;
    slru->cmp_handler = cmp_handler;


    znx_queue_t *lists = znx_thread_calloc(sizeof(znx_queue_t) * (size_t)nlist);
    for (int64_t i = 0; i < nlist; i++) {
        znx_queue_init(lists + i);
    }

    int64_t *lists_bytes = znx_thread_calloc((int64_t)sizeof(int64_t) * (size_t)nlist);
    int64_t *lists_n = znx_thread_calloc((int64_t)sizeof(int64_t) * (size_t)nlist);

    slru->lists = lists;
    slru->lists_bytes = lists_bytes;
    slru->lists_n = lists_n;

    znx_rbtree_init(&slru->tree, &slru->sentinel, znx_slru_item_cmp);

    return slru;
}


void
znx_slru_destroy(znx_slru_t *slru)
{
    znx_queue_t *list, *q;
    znx_slru_item_t *item;

    for (int i = 0; i < slru->nlist; i++) {
        list = slru->lists + i;
        while (!znx_queue_empty(list)) {
            q = znx_queue_last(list);
            znx_queue_remove(q);
            item = znx_queue_data(q, znx_slru_item_t, queue);
            znx_rbtree_delete(&slru->tree, &item->tree_node);
            if (slru->free_handler) {
                slru->free_handler(item->data);
            }
            znx_slru_item_destroy(item);
        }
    }

    znx_thread_free(slru->lists_n);
    znx_thread_free(slru->lists_bytes);
    znx_thread_free(slru->lists);
    znx_thread_free(slru);
}


static znx_bool_t
znx_slru_list_is_full(znx_slru_t *slru, int64_t list_idx)
{
    if (slru->max_item >= 0
        && *(slru->lists_n + list_idx) >= slru->max_item) {
        return ZNX_TRUE;
    }

    if (slru->max_bytes >= 0
        && *(slru->lists_bytes + list_idx) >= slru->max_bytes) {
        return ZNX_TRUE;
    }

    return ZNX_FALSE;
}


znx_bool_t
znx_slru_get(znx_slru_t *slru, void *target, void **data)
{
    *data = NULL;

    znx_rbtree_node_t    *node;
    znx_slru_item_t      *item;

    znx_slru_item_t target_item = {.data = target};
    node = znx_rbtree_lookup(&slru->tree, &target_item.tree_node);
    if (!node) {
        return ZNX_FALSE;
    }

    item = (znx_slru_item_t *)node;
    // already on fainal lists, move to head.
    if (item->idx == slru->nlist - 1) {
        znx_queue_remove(&item->queue);
        znx_queue_insert_head(slru->lists + item->idx, &item->queue);
        *data = item->data;
        return ZNX_TRUE;
    }

    int64_t item_size = 0;
    if (slru->size_get_handler != NULL) {
        item_size = (int64_t)(slru->size_get_handler(item->data));
    }

    *(slru->lists_bytes + item->idx) -= item_size;
    *(slru->lists_n + item->idx) -= 1;
    znx_queue_remove(&item->queue);
    item->idx += 1;

    // current list not full, insert.
    if (!znx_slru_list_is_full(slru, item->idx)) {
        *(slru->lists_bytes + item->idx) += item_size;
        *(slru->lists_n + item->idx) += 1;
        znx_queue_insert_head(slru->lists + item->idx, &item->queue);
        *data = item->data;
        return ZNX_TRUE;
    }

    // current list if full, swap it.
    znx_queue_t *back_q = znx_queue_last(slru->lists + item->idx);
    znx_slru_item_t *back_item = znx_queue_data(back_q, znx_slru_item_t, queue);
    znx_queue_remove(back_q);
    int64_t back_item_size = 0;
    if (slru->size_get_handler != NULL) {
        back_item_size = (int64_t)(slru->size_get_handler(back_item->data));
    }
    *(slru->lists_bytes + back_item->idx) -= back_item_size;
    *(slru->lists_n + back_item->idx) -= 1;
    back_item->idx -= 1;
    *(slru->lists_bytes + back_item->idx) += back_item_size;
    *(slru->lists_n + back_item->idx) += 1;
    znx_queue_insert_head(slru->lists + back_item->idx, &back_item->queue);

    *(slru->lists_bytes + item->idx) += item_size;
    *(slru->lists_n + item->idx) += 1;
    znx_queue_insert_head(slru->lists + item->idx, &item->queue);

    *data = item->data;
    return ZNX_TRUE;
}


static znx_bool_t
znx_slru_remove_helper(znx_slru_t *slru, void *target)
{
    znx_rbtree_node_t   *node;
    znx_slru_item_t     *item;
    int64_t             item_size = 0;

    znx_slru_item_t target_item = {.data = target};
    node = znx_rbtree_lookup(&slru->tree, &target_item.tree_node);
    if (!node) {
        return ZNX_FALSE;
    }

    item = (znx_slru_item_t *)node;
    if (slru->size_get_handler) {
        item_size = (int64_t)(slru->size_get_handler(item->data));
    }

    *(slru->lists_n + item->idx) -= 1;
    *(slru->lists_bytes + item->idx) -= item_size;

    znx_queue_remove(&item->queue);
    znx_rbtree_delete(&slru->tree, &item->tree_node);

    if (slru->free_handler) {
        slru->free_handler(item->data);
    }
    znx_slru_item_destroy(item);

    return ZNX_TRUE;
}


void
znx_slru_put(znx_slru_t *slru, void *data)
{
    // remove old data
    znx_slru_remove_helper(slru, data);

    znx_slru_item_t  *item;
    item = znx_slru_item_create(slru, data);

    int64_t         item_size = 0;
    if (slru->size_get_handler != NULL) {
        item_size = (int64_t)(slru->size_get_handler(item->data));
    }

    // the first list is full.
    if (znx_slru_list_is_full(slru, 0)) {
        if (!znx_queue_empty(slru->lists + 0)) {
            znx_queue_t          *back_q;
            znx_slru_item_t      *back_item;
            back_q = znx_queue_last(slru->lists + 0);
            back_item = znx_queue_data(back_q, znx_slru_item_t, queue);
            int64_t             back_item_size = 0;
            if (slru->size_get_handler != NULL) {
                back_item_size = (int64_t)(slru->size_get_handler(back_item->data));
            }
            *(slru->lists_n + 0) -= 1;
            *(slru->lists_bytes + 0) -= back_item_size;
            znx_queue_remove(&back_item->queue);
            znx_rbtree_delete(&slru->tree, &back_item->tree_node);
            if (slru->free_handler) {
                slru->free_handler(back_item->data);
            }
            znx_slru_item_destroy(back_item);
        }
    }

    *(slru->lists_n + 0) += 1;
    *(slru->lists_bytes + 0) += item_size;
    znx_rbtree_insert(&slru->tree, &item->tree_node);
    znx_queue_insert_head(slru->lists + 0, &item->queue);
}


znx_bool_t
znx_slru_remove(znx_slru_t *slru, void *target)
{
    return znx_slru_remove_helper(slru, target);
}


// will not increase the heat of the object
znx_bool_t
znx_slru_ge(znx_slru_t *slru, void *target, void **data)
{
    *data = NULL;

    znx_slru_item_t target_item = {.data = target};
    znx_rbtree_node_t *node;
    node = znx_rbtree_ge(&slru->tree, &target_item.tree_node);
    if (!node) {
        return ZNX_FALSE;
    }

    znx_slru_item_t *item;
    item = (znx_slru_item_t *)node;
    *data = item->data;
    return ZNX_TRUE;
}


// will not increase the heat of the object
znx_bool_t
znx_slru_le(znx_slru_t *slru, void *target, void **data)
{
    *data = NULL;

    znx_slru_item_t target_item = {.data = target};
    znx_rbtree_node_t *node;
    node =  znx_rbtree_le(&slru->tree, &target_item.tree_node);
    if (!node) {
        return ZNX_FALSE;
    }

    znx_slru_item_t *item;
    item = (znx_slru_item_t *)node;
    *data = item->data;
    return ZNX_TRUE;
}

